package cn.melonlib.graphql.tools.comp.service;

import cn.melonlib.graphql.cores.comp.utils.GraphqlScanner;
import cn.melonlib.graphql.model.enums.GraphqlQueryMode;
import cn.melonlib.graphql.model.enums.GraphqlScalarType;
import cn.melonlib.graphql.model.metadata.GraphqlArgument;
import cn.melonlib.graphql.model.metadata.GraphqlFunction;
import cn.melonlib.objects.maps.JsonMap;
import freemarker.core.Environment;
import freemarker.ext.beans.BeansWrapper;
import freemarker.ext.beans.BeansWrapperBuilder;
import freemarker.template.*;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;

import javax.annotation.Resource;
import java.io.IOException;
import java.io.StringWriter;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Service
public class GraphqlTemplateOutputService {

    @Resource
    private GraphqlScanner graphqlScanner;

    @Resource
    private FreeMarkerConfigurer freemarkerConfig;



    private JsonMap<String> tsScalars=JsonMap.createMap();
    {
        Arrays.stream(GraphqlScalarType.values()).forEach(t->{
            tsScalars.append(t.getScalarType().getName(),t.getTsType());
        });
    }

    private TemplateMethodModelEx isScalar= list -> tsScalars.containsKey(list.get(0).toString());
    private TemplateMethodModelEx getType = list -> {
        Object type=graphqlScanner.getTypes().get(list.get(0).toString()).get(list.get(1).toString());
        if(Objects.nonNull(type)){
            return type;
        }
        return graphqlScanner.getAllViewTypes().get(list.get(1).toString());
    };

    private TemplateMethodModelEx getQueryStatement= list -> {
        try {
            return queryStatement(list.get(0).toString(), (GraphqlFunction) ((BeansWrapper)freemarkerConfig.getConfiguration().getObjectWrapper()).unwrap((TemplateModel) list.get(1))
                    ,(Integer) ((BeansWrapper)freemarkerConfig.getConfiguration().getObjectWrapper()).unwrap((TemplateModel) list.get(2)),(Boolean) ((BeansWrapper)freemarkerConfig.getConfiguration().getObjectWrapper()).unwrap((TemplateModel) list.get(3))
            );
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    };

    public String getGraphqlTypes(String namespace) throws IOException, TemplateException {
        Template declareTpl = freemarkerConfig.getConfiguration().getTemplate("graphql/types.graphql", "utf-8");
        StringWriter out=new StringWriter();
        declareTpl.process(JsonMap.createMap()
                .append("views",graphqlScanner.getViewTypes().get(namespace).entrySet().stream().map(Map.Entry::getValue).toList())
                        .append("inputs",graphqlScanner.getInputTypes().get(namespace).entrySet().stream().map(Map.Entry::getValue).toList())
                ,out);
        return out.toString();
    }

    public String getGraphqlTsTypes(String namespace) throws IOException, TemplateException {
        Template declareTpl = freemarkerConfig.getConfiguration().getTemplate("ts/types.ts", "utf-8");
        StringWriter out=new StringWriter();
        declareTpl.process(JsonMap.createMap()
                        .append("getTsType", new TemplateMethodModelEx() {
                            public Object exec(List list) throws TemplateModelException {
                                String type=StringUtils.defaultIfBlank(tsScalars.get(list.get(0).toString()),list.get(0).toString());
                                return type;
                            }
                        })
                .append("views",graphqlScanner.getViewTypes().get(namespace).entrySet().stream().map(Map.Entry::getValue).toList())
                        .append("inputs",graphqlScanner.getInputTypes().get(namespace).entrySet().stream().map(Map.Entry::getValue).toList())
                ,out);
        return out.toString();
    }

    public String getGraphqlFunctions(String namespace) throws IOException, TemplateException {
        Template declareTpl = freemarkerConfig.getConfiguration().getTemplate("graphql/functions.graphql", "utf-8");
        List<GraphqlFunction> queries=graphqlScanner.getQueries().get(namespace).entrySet().stream().map(Map.Entry::getValue).toList();
        List<String> moreTypes=queries.stream().filter(q-> Objects.equals(q.getQueryMode(), GraphqlQueryMode.OFFSET)).map(GraphqlFunction::getType).distinct().toList();
        StringWriter out=new StringWriter();
        declareTpl.process(JsonMap.createMap()
                        .append("moreTypes",moreTypes)
                        .append("queries",queries)
                        .append("mutations",graphqlScanner.getMutations().get(namespace).entrySet().stream().map(Map.Entry::getValue).toList())
                ,out);
        return out.toString();
    }

    public String getGraphqlTsFunctions(String namespace) throws IOException, TemplateException {
        Template declareTpl = freemarkerConfig.getConfiguration().getTemplate("ts/functions.ts", "utf-8");
        List<GraphqlFunction> queries=graphqlScanner.getQueries().get(namespace).entrySet().stream().map(Map.Entry::getValue).toList();
        List<GraphqlFunction> mutations=graphqlScanner.getMutations().get(namespace).entrySet().stream().map(Map.Entry::getValue).toList();
        List<String> moreTypes=queries.stream().filter(q-> Objects.equals(q.getQueryMode(), GraphqlQueryMode.OFFSET)).map(GraphqlFunction::getType).distinct().toList();
        StringWriter out=new StringWriter();
        declareTpl.process(JsonMap.createMap()
                        .append("types", Stream.concat(Stream.concat(queries.stream().map(GraphqlFunction::getType).filter(t->!tsScalars.containsKey(t)),
                                mutations.stream().map(GraphqlFunction::getType).filter(t->!tsScalars.containsKey(t)))
                                ,Stream.concat(queries.stream().flatMap(f->f.getArguments().stream()).map(GraphqlArgument::getType).filter(t->!tsScalars.containsKey(t))
                                        ,mutations.stream().flatMap(f->f.getArguments().stream()).map(GraphqlArgument::getType).filter(t->!tsScalars.containsKey(t))
                                        )
                                ).distinct().toList() )
                        .append("getTsType", (TemplateMethodModelEx) list -> StringUtils.defaultIfBlank(tsScalars.get(list.get(0).toString()),list.get(0).toString()))
                        .append("moreTypes",moreTypes)
                        .append("queries",queries)
                        .append("mutations",mutations)
                ,out);
        return out.toString();
    }

    public String getGraphqlQueryTsConst(String namespace,int deepLevel) throws IOException, TemplateException {
        Template declareTpl = freemarkerConfig.getConfiguration().getTemplate("ts/query_const.ts", "utf-8");
        StringWriter out=new StringWriter();
        declareTpl.process(JsonMap.createMap().append("functions",graphqlScanner.getFunctions().get(namespace).values())
                        .append("namespace",namespace).append("deepLevel",deepLevel)
                .append("getQueryStatement",getQueryStatement),out);
        return out.toString().lines().filter(StringUtils::isNotBlank).collect(Collectors.joining("\r\n"));

    }

    public String queryStatement(String namespace,GraphqlFunction graphqlFunction,int deepLevel,boolean enclose) throws Exception{
        Template declareTpl = freemarkerConfig.getConfiguration().getTemplate("blocks/statement.ftl", "utf-8");
        StringWriter out=new StringWriter();
        declareTpl.process(JsonMap.createMap().append("graphqlFunction",graphqlFunction).append("enclose",enclose).append("namespace",namespace)
                        .append("isScalar",isScalar).append("getType",getType).append("deepLevel",deepLevel)
                ,out);
        return out.toString().lines().filter(StringUtils::isNotBlank).collect(Collectors.joining("\r\n"));
    }
}
